#!/usr/bin/env bash
#
# mktheorytraits
# Morgan Deters <mdeters@cs.nyu.edu> for cvc5
# Copyright (c) 2010-2013  The cvc5 Project
#
# The purpose of this script is to create theory_traits.h from a template
# and a list of theory kinds.
#
# Invocation:
#
#   mktheorytraits template-file theory-kind-files...
#
# Output is to standard out.
#

copyright=2010-2022

filename=`basename "$1" | sed 's,_template,,'`

cat <<EOF
/******************************************************************************
 * This file is part of the cvc5 project.
 *
 * Copyright (c) $copyright by the authors listed in the file AUTHORS
 * in the top-level source directory and their institutional affiliations.
 * All rights reserved.  See the file COPYING in the top-level source
 * directory for licensing information.
 * ****************************************************************************
 *
 * This header file automatically generated by:
 *
 *     $0 $@
 *
 * for the cvc5 project.
 */

EOF

me=$(basename "$0")

template=$1; shift

theory_traits=
theory_includes=
theory_constructors=

type_enumerator_includes=
mk_type_enumerator_cases=

theory_has_check="false"
theory_has_propagate="false"
theory_has_ppStaticLearn="false"
theory_has_notifyRestart="false"
theory_has_presolve="false"

theory_stable_infinite="false"
theory_finite="false"
theory_polite="false"
theory_parametric="false"

rewriter_class=
rewriter_header=

theory_id=
theory_class=

type_constants=
type_kinds=

seen_theory=false
seen_theory_builtin=false

function theory {
  # theory ID T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  # this script doesn't care about the theory class information, but
  # makes does make sure it's there
  seen_theory=true
  if [ "$1" = THEORY_BUILTIN ]; then
    if $seen_theory_builtin; then
      echo "$kf:$lineno: error: \"builtin\" theory redefined" >&2
      exit 1
    fi
    seen_theory_builtin=true
  elif [ -z "$1" -o -z "$2" -o -z "$3" ]; then
    echo "$kf:$lineno: error: \"theory\" directive missing class or header argument" >&2
    exit 1
  elif ! expr "$2" : '\(::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class \`$2' isn't fully-qualified (e.g., ::cvc5::internal::theory::foo)" >&2
  elif ! expr "$2" : '\(::cvc5::internal::theory::*\)' >/dev/null; then
    echo "$kf:$lineno: warning: theory class not under ::cvc5::internal::theory namespace" >&2
  fi

  theory_id="$1"
  theory_class="$2"

  theory_header="$3"
  theory_includes="${theory_includes}#include \"$theory_header\"
"
}

function alternate {
  # alternate ID name T header

  lineno=${BASH_LINENO[0]}

  if $seen_theory; then
    echo "$kf:$lineno: error: multiple theories defined in one file !?" >&2
    exit 1
  fi

  seen_theory=true
  seen_endtheory=true

  theory_header="$4"
  theory_includes="${theory_includes}#include \"$theory_header\"
"
}

function rewriter {
  # rewriter class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen

  rewriter_class="$1"
  rewriter_header="$2"

  theory_includes="${theory_includes}#include \"$2\"
"
}

function endtheory {
  # endtheory
  lineno=${BASH_LINENO[0]}
  check_theory_seen

  seen_endtheory=true

  theory_constructors="${theory_constructors}
    case $theory_id:
\$alternate_for_$theory_id
      engine->addTheory< $theory_class >($theory_id);
      return;
"

  theory_traits="${theory_traits}
template<>
struct TheoryTraits<${theory_id}> {
    // typedef ${theory_class} theory_class;
    typedef ${rewriter_class} rewriter_class;

    static const bool isStableInfinite = ${theory_stable_infinite};
    static const bool isFinite = ${theory_finite};
    static const bool isPolite = ${theory_polite};
    static const bool isParametric = ${theory_parametric};

    static const bool hasCheck = ${theory_has_check};
    static const bool hasPropagate = ${theory_has_propagate};
    static const bool hasPpStaticLearn = ${theory_has_ppStaticLearn};
    static const bool hasNotifyRestart = ${theory_has_notifyRestart};
    static const bool hasPresolve = ${theory_has_presolve};
};/* struct TheoryTraits<${theory_id}> */
"

  # warnings about theory content and properties
  dir="$(dirname "$kf")/../../"
  if [ -e "$dir/$theory_header" ]; then
    for function in propagate ppStaticLearn notifyRestart presolve; do
       if eval "\$theory_has_$function"; then
         grep '\<'"$function"' *\((\|;\)' "$dir/$theory_header" | grep -vq '^ */\(/\|\*\)' ||
           echo "$kf: warning: $theory_class has property \"$function\" in its kinds file but doesn't appear to declare the function" >&2
       else
         grep '\<'"$function"' *\((\|;\)' "$dir/$theory_header" | grep -vq '^ */\(/\|\*\)' &&
           echo "$kf: warning: $theory_class does not have property \"$function\" in its kinds file but appears to declare the function" >&2
       fi
     done
  else
    echo "$me: warning: theory header \"$theory_header\" does not exist or is unreadable" >&2
  fi

  theory_has_check="false"
  theory_has_propagate="false"
  theory_has_ppStaticLearn="false"
  theory_has_notifyRestart="false"
  theory_has_presolve="false"

  theory_stable_infinite="false"
  theory_finite="false"
  theory_polite="false"
  theory_parametric="false"

  rewriter_class=
  rewriter_header=

  theory_id=
  theory_class=

  type_constants=
  type_kinds=

  lineno=${BASH_LINENO[0]}
}

function enumerator {
  # enumerator KIND enumerator-class header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  type_enumerator_includes="${type_enumerator_includes}
#include \"$3\""
  if expr "$type_constants" : '.* '"$1"' ' &>/dev/null; then
    mk_type_enumerator_type_constant_cases="${mk_type_enumerator_type_constant_cases}
    case $1:
      return new $2(type, tep);
"
  elif expr "$type_kinds" : '.* '"$1"' ' &>/dev/null; then
    mk_type_enumerator_cases="${mk_type_enumerator_cases}
  case kind::$1:
    return new $2(type, tep);
"
  else
    echo "$kf:$lineno: error: don't know anything about $1; enumerator must appear after definition" >&2
    echo "type_constants: $type_constants" >&2
    echo "type_kinds    : $type_kinds" >&2
    exit 1
  fi
}

function typechecker {
  # typechecker header
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function typerule {
  # typerule OPERATOR typechecking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function construle {
  # construle OPERATOR isconst-checking-class
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function properties {
  # properties property*
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  while (( $# ));
  do
    property="$1"
    case "$property" in
       finite) theory_finite="true";;
       stable-infinite) theory_stable_infinite="true";;
       parametric) theory_parametric="true";;
       polite) theory_polite="true";;
       check) theory_has_check="true";;
       propagate) theory_has_propagate="true";;
       ppStaticLearn) theory_has_ppStaticLearn="true";;
       presolve) theory_has_presolve="true";;
       notifyRestart) theory_has_notifyRestart="true";;
       *) echo "$kf:$lineno: error: unknown theory property \"$property\"" >&2; exit 1;;
    esac
    shift
  done
}

function sort {
  # sort TYPE cardinality [well-founded ground-term header | not-well-founded] ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  if [ "$3" = well-founded ]; then
    register_sort "$1" "$2" "$6"
  else
    register_sort "$1" "$2" "$4"
  fi
}

function cardinality {
  # cardinality TYPE cardinality-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function well-founded {
  # well-founded TYPE wellfoundedness-computer [header]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
}

function variable {
  # variable K ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  register_kind "$1" 0 "$2"
}

function operator {
  # operator K #children ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  register_kind "$1" "$2" "$3"
}

function parameterized {
  # parameterized K1 K2 #children ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  register_kind "$1" "$3" "$4"
}

function constant {
  # constant K T Hasher header ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  register_kind "$1" 0 "$5"
}

function nullaryoperator {
  # nullaryoperator K ["comment"]
  lineno=${BASH_LINENO[0]}
  check_theory_seen
  register_kind "$1" 0 "$2"
}

function register_sort {
  id=$1
  cardinality=$2
  comment=$3
  type_constant_to_theory_id="${type_constant_to_theory_id}   case $id: return $theory_id; break;
"
  type_constants="${type_constants} $1 "
}

function register_kind {
  r=$1
  nc=$2
  comment=$3
  kind_to_theory_id="${kind_to_theory_id}   case kind::$r: return $theory_id; break;
"
  type_kinds="${type_kinds} $1 "
}

function check_theory_seen {
  if $seen_endtheory; then
    echo "$kf:$lineno: error: command after \"endtheory\" declaration (endtheory has to be last)" >&2
    exit 1
  fi
  if ! $seen_theory; then
    echo "$kf:$lineno: error: no \"theory\" declaration found (it has to be first)" >&2
    exit 1
  fi
}

function check_builtin_theory_seen {
  if ! $seen_theory_builtin; then
    echo "$me: warning: no declaration for the builtin theory found" >&2
  fi
}

while [ $# -gt 0 ]; do
  kf=$1
  seen_theory=false
  seen_endtheory=false
  b=$(basename $(dirname "$kf"))
  source "$kf"
  if ! $seen_theory; then
    echo "$kf: error: no theory content found in file!" >&2
    exit 1
  fi
  if ! $seen_endtheory; then
    echo "$kf:$lineno: error: no \"endtheory\" declaration found (it is required at the end)" >&2
    exit 1
  fi
  shift
done
check_builtin_theory_seen

## output

eval "theory_constructors=\"$theory_constructors\""

text=$(cat "$template")
for var in \
    theory_traits \
    theory_includes \
    theory_constructors \
    template \
    type_enumerator_includes \
    mk_type_enumerator_type_constant_cases \
    mk_type_enumerator_cases \
    ; do
  eval text="\${text//\\\$\\{$var\\}/\${$var}}"
done
error=`expr "$text" : '.*\${\([^}]*\)}.*'`
if [ -n "$error" ]; then
  echo "$template:0: error: undefined replacement \${$error}" >&2
  exit 1
fi
echo "$text"
